﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Content.Pipeline;
using System.Xml;
using Microsoft.Xna.Framework.Content.Pipeline.Serialization.Intermediate;

namespace OpenST
{
    public class Composition
    {
        public const int CompositionMaxSizeX = 256;
        public const int CompositionMaxSizeY = 256;

        public int TileUpdateInterval{get; set;}
        private int TileUpdatecounter = 0;

        public Tiles.BaseTile[,] Tiles;
        private Game theGame;
        public List<Bug> Bugs;
        public float Speed{get; set;}
        public string Title{get;set;}
        public string Author{get;set;}

        public bool Playing;

        public Composition(Game game)
        {
            Tiles = new Tiles.BaseTile[CompositionMaxSizeX, CompositionMaxSizeY];
            Bugs = new List<Bug>();
            Playing = false;
            theGame = game;
            TileUpdateInterval = 250;
            Speed = 4.0f;
        }

        public void NewComposition()
        {
            Title = "Untitled";
            Author = "Unnamed Author";
            Speed = 4.0f;
            TileUpdatecounter = 250;
            for (int x = 0; x < CompositionMaxSizeX; x++)
            {
                for (int y = 0; y < CompositionMaxSizeY; y++)
                {
                    Tiles[x, y] = new Tiles.BlankTile(theGame.Content);
                    Tiles[x, y].OnCreate(x, y);
                }
            }
            Bugs.Clear();
        }

        public void Reset()
        {
            Playing = false;
            foreach (Bug bug in Bugs)
            {
                bug.Reset();
            }
        }

        public void SavePNG(string PNG)
        {
            DataFormats.NoteColors nc = theGame.Content.Load<DataFormats.NoteColors>("NoteColors");    

            Texture2D comp;
            Color[] pixelData2 = new Color[CompositionMaxSizeX * CompositionMaxSizeY];
            comp = new Texture2D(theGame.GraphicsDevice, CompositionMaxSizeX, CompositionMaxSizeX);
            comp.GetData<Color>(pixelData2);
            int i = 0;
            for (int y = 0; y < CompositionMaxSizeY; y++)
            {
                for (int x = 0; x < CompositionMaxSizeX; x++)
                {
                    if (Tiles[x, y].GetsSaved == false)
                    {
                        pixelData2[i] = new Color(0, 0, 0, 0); 
                    }
                    else
                    {
                        if (Tiles[x, y].color)
                        {
                            pixelData2[i] = nc.colors[Tiles[x, y].NoteNumber];
                        }
                        else
                        {
                            pixelData2[i] = nc.shades[Tiles[x, y].NoteNumber];
                        }
                    }
                    i++;
                }
            }
            comp.SetData<Color>(pixelData2);
            System.IO.FileStream fstr = new System.IO.FileStream(PNG, System.IO.FileMode.CreateNew, System.IO.FileAccess.ReadWrite);
            comp.SaveAsPng(fstr, CompositionMaxSizeX, CompositionMaxSizeY);
        }

        public void Save(string XML)
        {
            DataFormats.Composition.Composition CompData = new DataFormats.Composition.Composition();
            CompData.ChangedTiles = new List<DataFormats.Composition.Tile>();
            CompData.Bugs = new List<DataFormats.Composition.Bug>();

            CompData.Speed = Speed;
            CompData.Author = Author;
            CompData.Title = Title;
            CompData.TileUpdateInterval = TileUpdateInterval;

            for(int x = 0; x < CompositionMaxSizeX; x++)
            {
                for (int y = 0; y < CompositionMaxSizeX; y++)
                {
                    if (Tiles[x, y].GetsSaved == true)
                    {
                        DataFormats.Composition.Tile savetile = new DataFormats.Composition.Tile();
                        savetile.X = x;
                        savetile.Y = y;
                        savetile.Note = Tiles[x, y].NoteNumber;
                        savetile.color = Tiles[x, y].color;
                        savetile.tiletype = Tiles[x, y].GetType().ToString();
                        savetile.TileData = Tiles[x, y].TileData;
                        CompData.ChangedTiles.Add(savetile);
                    }
                }
            }

            foreach (Bug bug in Bugs)
            {
                DataFormats.Composition.Bug savebug = new DataFormats.Composition.Bug();
                savebug.BugType = bug.BugXML;
                savebug.Pan = bug.Pan;
                savebug.Speed = bug.Speed;
                savebug.StartX = bug.StartX;
                savebug.StartY = bug.StartY;
                savebug.Volume = bug.Volume;
                savebug.StartDirection = (int)bug.StartDirection;
                savebug.PitchAdjust = bug.StartPitchAdjust;
                CompData.Bugs.Add(savebug);
            }

            // Save the XML
            XmlWriterSettings settings = new XmlWriterSettings();
            settings.Indent = true;

            using (XmlWriter writer = XmlWriter.Create(XML, settings))
            {
                IntermediateSerializer.Serialize(writer, CompData, null);
            }
        }

        public void Load(string XML)
        {
            
            XmlReader Xmread = XmlReader.Create(XML);
            DataFormats.Composition.Composition CompData = IntermediateSerializer.Deserialize<DataFormats.Composition.Composition>(Xmread, null);

            NewComposition(); // Reset
            Title = CompData.Title;
            Author = CompData.Author;
            Speed = CompData.Speed;
            TileUpdateInterval = CompData.TileUpdateInterval;

            foreach (DataFormats.Composition.Bug loadbug in CompData.Bugs)
            {
                Bug b = AddBug(loadbug.BugType);
                b.StartX = loadbug.StartX;
                b.StartY = loadbug.StartY;
                b.Speed = loadbug.Speed;
                b.Pan = loadbug.Pan;
                b.StartPitchAdjust = loadbug.PitchAdjust;
                b.StartDirection = (Bug.Direction)loadbug.StartDirection;
                b.Reset();
            }

            foreach (DataFormats.Composition.Tile loadTile in CompData.ChangedTiles)
            {
                int x = loadTile.X;
                int y = loadTile.Y;

                Tiles[x, y] = (Tiles.BaseTile)System.Reflection.Assembly.GetExecutingAssembly().CreateInstance(loadTile.tiletype, false, System.Reflection.BindingFlags.Default, null, new Object[]{theGame.Content }, null, null);
                Tiles[x, y].color = loadTile.color;
                Tiles[x, y].NoteNumber = loadTile.Note;
                Tiles[x, y].TileData = loadTile.TileData;
            }
        }

        public Bug AddBug(string bugxml)
        {
            Bug newbug = new Bug();
            newbug.LoadContent(theGame.Content, bugxml);
            newbug.SetComposition(this);
            Bugs.Add(newbug);
            return newbug;
        }

        public void Update(GameTime gametime)
        {
            int upmils = gametime.ElapsedGameTime.Milliseconds;
            if (Playing)
            {
                foreach (Bug bug in Bugs)
                {
                    bug.Update(upmils);
                }
            }

            TileUpdatecounter += gametime.ElapsedGameTime.Milliseconds;
            /* I'm still not sure about this.
             * Advanced tiles MAY require that they be updated
             * but it is also VERY slow. 
             * Maybe the answer is to update once every second? */
            // TODO: Tile Loop
            if (TileUpdatecounter >= TileUpdateInterval)
            {
                foreach (Tiles.BaseTile tile in Tiles)
                {
                    tile.Update(gametime);
                }

                TileUpdatecounter = 0;
            }
        }

    }
}
